﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Collections;
using System.IO;
using System.Threading;
using System.Security.Cryptography;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;

namespace PHPFileChecker
{
    class Worker
    {
        public event Notify_Delegate OnCheckShow;
        public event Notify_Delegate OnCheckResult;
        public event Notify_Delegate OnCheckComplete;

        private WorkOption co;

        private int tc = 0;
        private int totalFiles = 0;
        private int errorFiles = 0;
        private int matchFiles = 0;

        private ManualResetEvent mre = new ManualResetEvent(false);

        private System.Timers.Timer timer = new System.Timers.Timer();
        private int ticket = Config.MAX_TICKET;

        private int f1 = 0;
        private int f2 = 0;
        private int f3 = 0;
        private int f4 = 0;
        private int f5 = 0;

        public Worker()
        {
            timer.Interval = 200;
            timer.AutoReset = true;
            timer.Elapsed += (ts, te) =>
            {
                if (ticket < Config.MAX_TICKET)
                {
                    Interlocked.Increment(ref ticket);
                }
            };
        }

        public void startCheck(object obj)
        {
            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();

                co = (WorkOption)obj;
                timer.Start();
                tc = 0;
                totalFiles = 0;
                getFiles(co.Path);
                if (matchFiles > 0)
                {
                    mre.WaitOne();
                }
                timer.Stop();

                sw.Stop();
                string info = "检查完成，用时：" + sw.Elapsed.TotalSeconds.ToString() + "秒，文件：" + totalFiles + "，匹配：" + matchFiles.ToString() + "，错误：" + errorFiles.ToString() + "，计数：" + f1.ToString() + "-" + f2.ToString() + "-" + f3.ToString() + "-" + f4.ToString() + "-" + f5.ToString();
                if (OnCheckComplete != null)
                {
                    OnCheckComplete(info);
                }

                Console.WriteLine(info);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        private void getFiles(string path)
        {
            DirectoryInfo dir = new DirectoryInfo(path);

            //当前目录下所有子目录下的全部文件
            foreach (DirectoryInfo d in dir.GetDirectories())
            {
                getFiles(path + @"\" + d.Name);
            }

            //当前目录下所有文件（不包括子目录）
            foreach (FileInfo f in dir.GetFiles())
            {
                totalFiles++;
                string filePath = (path + @"\" + f.Name).Trim();
                if (co.FileType.Contains(Path.GetExtension(filePath).ToLower()))
                {
                    matchFiles++;
                    Interlocked.Increment(ref tc);
                    mre.Reset();
                    ThreadPool.QueueUserWorkItem(new WaitCallback(checkFile), filePath);
                }
            }
        }

        private void checkFile(object obj)
        {
            try
            {
                string filePath = (string)obj;
                if (OnCheckShow != null && ticket > 0)
                {
                    Interlocked.Decrement(ref ticket);
                    OnCheckShow(filePath);
                }

                string flag = "";
                string newFile = "";
                using (FileStream fs = new FileStream(filePath, FileMode.Open))
                {
                    long daStart = 0, daEnd = 0, dbStart = 0, dbEnd = 0;
                    bool fsHasBom = hasBom(fs);
                    if (co.CheckBOM && fsHasBom)
                    {
                        Interlocked.Increment(ref f1);
                        flag += "[BOM]";
                    }
                    if (co.ClearBOM && fsHasBom)
                    {
                        daStart = 0;
                        daEnd = 3;
                        Interlocked.Increment(ref f2);
                    }
                    if (co.CheckWhiteSpace && @".php" == Path.GetExtension(filePath).ToLower())
                    {
                        long p = searchPHPHeader(fs);
                        long whiteStart = fsHasBom ? 3 : 0;
                        bool fsHasWhiteSpace = (p > whiteStart);
                        if (co.CheckWhiteSpace && fsHasWhiteSpace)
                        {
                            Interlocked.Increment(ref f3);
                            flag += "[空白]";
                        }
                        if (co.ClearWhiteSpace && fsHasWhiteSpace)
                        {
                            dbStart = whiteStart;
                            dbEnd = p;
                            Interlocked.Increment(ref f4);
                        }
                    }
                    if (co.CheckUTF8)
                    {
                        if (!isUTF8(fs))
                        {
                            Interlocked.Increment(ref f5);
                            flag += "[非UTF8]";
                        }
                    }
                    newFile = cutFile(fs, daStart, daEnd, dbStart, dbEnd);
                }
                if (!string.IsNullOrEmpty(newFile))
                {
                    if (co.BackupFile)
                    {
                        File.Move(filePath, filePath + @".bak." + Guid.NewGuid().ToString());
                    }
                    else
                    {
                        File.Delete(filePath);
                    }
                    File.Move(newFile, filePath);
                }
                if (OnCheckResult != null && !string.IsNullOrEmpty(flag))
                {
                    OnCheckResult(flag + " " + filePath);
                }
            }
            catch (Exception ex)
            {
                Interlocked.Increment(ref errorFiles);
                Console.WriteLine(ex.ToString());
            }
            if (Interlocked.Decrement(ref tc) == 0)
            {
                mre.Set();
            }
        }

        private bool hasBom(FileStream fs)
        {
            fs.Position = 0;
            int b1 = fs.ReadByte();
            int b2 = fs.ReadByte();
            int b3 = fs.ReadByte();
            if (b1 == 0xEF && b2 == 0xBB && b3 == 0xBF)
            {
                return true;
            }
            return false;
        }

        private long searchPHPHeader(FileStream fs)
        {
            fs.Position = 0;
            byte[] s = Encoding.ASCII.GetBytes("<?php");
            long i = 0, p = 0;
            while (i < s.Length && fs.Position < fs.Length)
            {
                if (fs.ReadByte() == (int)s[i])
                {
                    i++;
                }
                else
                {
                    p++;
                    fs.Position = p;
                    i = 0;
                }
            }
            return i == s.Length ? p : -1;
        }

        private bool isUTF8(FileStream fs)
        {
            fs.Position = 0;
            while (fs.Position < fs.Length)
            {
                int b1 = fs.ReadByte();
                if ((b1 & 0x80) == 0x00)
                {
                    continue;
                }
                if ((b1 & 0xE0) == 0xC0)
                {
                    int b2 = fs.ReadByte();
                    if ((b2 & 0xC0) == 0x80)
                    {
                        continue;
                    }
                }
                if ((b1 & 0xF0) == 0xE0)
                {
                    int b2 = fs.ReadByte();
                    int b3 = fs.ReadByte();
                    if ((b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80)
                    {
                        continue;
                    }
                }
                if ((b1 & 0xF8) == 0xF0)
                {
                    int b2 = fs.ReadByte();
                    int b3 = fs.ReadByte();
                    int b4 = fs.ReadByte();
                    if ((b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80 && (b4 & 0xC0) == 0x80)
                    {
                        continue;
                    }
                }
                return false;
            }
            return true;
        }

        private string cutFile(FileStream fs, long daStart, long daEnd, long dbStart, long dbEnd)
        {
            long dStart = 0, dEnd = 0;
            if (daEnd == 0)
            {
                dStart = dbStart;
                dEnd = dbEnd;
            }
            else
            {
                if (dbEnd == 0)
                {
                    dStart = daStart;
                    dEnd = daEnd;
                }
                else
                {
                    dStart = daStart;
                    dEnd = dbEnd;
                }
            }
            if (dEnd == 0)
            {
                return "";
            }

            string newFile = fs.Name + "." + Guid.NewGuid().ToString();
            using (FileStream nfs = new FileStream(newFile, FileMode.Create))
            {
                byte[] buffer = new byte[1024 * 1024];
                int readCount = 0;
                fs.Position = 0;
                if (dStart != 0)
                {
                    readCount = fs.Read(buffer, 0, (int)dStart);
                    nfs.Write(buffer, 0, readCount);
                }
                fs.Position = dEnd;
                do
                {
                    readCount = fs.Read(buffer, 0, buffer.Length);
                    nfs.Write(buffer, 0, readCount);

                } while (readCount > 0);
            }
            return newFile;
        }
    }
}
